/*
 * Copyright (C) 2017 Jared Rummler
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.jaredrummler.ohos.colorpicker;

import com.jaredrummler.ohos.colorpicker.core.Drawable;
import com.jaredrummler.ohos.colorpicker.core.TypedArray;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.*;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.agp.window.dialog.ToastDialog;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;

import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;

/**
 * This class draws a panel which which will be filled with a color which can be set. It can be used to show the
 * currently selected color which you will get from the {@link ColorPickerView}.
 */
public class ColorPanelView extends Component implements Component.DrawTask, Component.EstimateSizeListener, Component.LayoutRefreshedListener {

    private final static int DEFAULT_BORDER_COLOR = 0xFF6E6E6E;

    private Drawable alphaPattern;
    private Paint borderPaint;
    private Paint colorPaint;
    private Paint alphaPaint;
    private Paint originalPaint;
    private Rect drawingRect;
    private Rect colorRect;
    private RectFloat centerRect = new RectFloat();
    private boolean showOldColor;

    /* The width in pixels of the border surrounding the color panel. */
    private int borderWidthPx;
    private int borderColor = DEFAULT_BORDER_COLOR;
    private int color = Color.BLACK.getValue();
    private int shape;

    public ColorPanelView(Context context) {
        this(context, null);
    }

    public ColorPanelView(Context context, AttrSet attrs) {
        super(context, attrs);
        init(context, attrs);

        addDrawTask(this);
        setEstimateSizeListener(this);
        setLayoutRefreshedListener(this);
    }

    public ColorPanelView(Context context, AttrSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs);
    }

    private void init(Context context, AttrSet attrs) {
        TypedArray a = new TypedArray(attrs);
        String shapeStr = a.getString("cpv_colorShape", "circle");
        int Shape;
        switch (shapeStr){
            case "square":
                shape = ColorShape.SQUARE;
                break;
            case "circle":
            default:
                shape = ColorShape.CIRCLE;
                break;
        }

        showOldColor = a.getBoolean("cpv_showOldColor", false);
        if (showOldColor && shape != ColorShape.CIRCLE) {
            throw new IllegalStateException("Color preview is only available in circle mode");
        }
        borderColor = a.getColor("cpv_borderColor", DEFAULT_BORDER_COLOR);
        a.recycle();
        borderWidthPx = DrawingUtils.dpToPx(context, 1);
        borderPaint = new Paint();
        borderPaint.setAntiAlias(true);
        colorPaint = new Paint();
        colorPaint.setAntiAlias(true);
        if (showOldColor) {
            originalPaint = new Paint();
        }
        if (shape == ColorShape.CIRCLE) {
            PixelMap bitmap = null;
            InputStream inputStream = null;
            try {
                inputStream = getContext().getResourceManager().getResource(ResourceTable.Media_cpv_alpha);
                ImageSource.SourceOptions srcOpts = new ImageSource.SourceOptions();
                ImageSource imageSource = ImageSource.create(inputStream, srcOpts);
                ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
                bitmap = imageSource.createPixelmap(decodingOptions);
            } catch (NotExistException | IOException e) {
                    e.printStackTrace();
            }

            alphaPaint = new Paint();
            alphaPaint.setAntiAlias(true);
            PixelMapShader shader = new PixelMapShader(new PixelMapHolder(bitmap), Shader.TileMode.REPEAT_TILEMODE, Shader.TileMode.REPEAT_TILEMODE);
            alphaPaint.setShader(shader, Paint.ShaderType.PIXELMAP_SHADER);
        }
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (drawingRect == null) {
            onRefreshed(this);
        }
        borderPaint.setColor(new Color(borderColor));
        colorPaint.setColor(new Color(color));
        if (shape == ColorShape.SQUARE) {
            if (borderWidthPx > 0) {
                canvas.drawRect(drawingRect, borderPaint);
            }
            if (alphaPattern != null) {
                alphaPattern.draw(canvas);
            }
            canvas.drawRect(colorRect, colorPaint);
        } else if (shape == ColorShape.CIRCLE) {
            final int outerRadius = getEstimatedWidth() / 2;
            if (borderWidthPx > 0) {
                canvas.drawCircle(getEstimatedWidth() / 2, getEstimatedHeight() / 2, outerRadius, borderPaint);
            }
            if (Color.alpha(color) < 255) {
                canvas.drawCircle(getEstimatedWidth() / 2, getEstimatedHeight() / 2, outerRadius - borderWidthPx, alphaPaint);
            }
            if (showOldColor) {
                canvas.drawArc(centerRect, new Arc(90, 180, true), originalPaint);
                canvas.drawArc(centerRect, new Arc(270, 180, true), colorPaint);
            } else {
                canvas.drawCircle(getEstimatedWidth() / 2, getEstimatedHeight() / 2, outerRadius - borderWidthPx, colorPaint);
            }
        }
    }

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        int width = Component.EstimateSpec.getSize(widthMeasureSpec);
        int height = Component.EstimateSpec.getSize(heightMeasureSpec);
        setEstimatedSize(
                Component.EstimateSpec.getChildSizeWithMode(width, width, Component.EstimateSpec.PRECISE),
                Component.EstimateSpec.getChildSizeWithMode(height, height, Component.EstimateSpec.PRECISE));
        return true;
    }

    //    @Override
    protected void onSizeChanged(int w, int h) {
        if (shape == ColorShape.SQUARE || showOldColor) {
            drawingRect = new Rect();
            drawingRect.left = getPaddingLeft();
            drawingRect.right = w - getPaddingRight();
            drawingRect.top = getPaddingTop();
            drawingRect.bottom = h - getPaddingBottom();
            if (showOldColor) {
                setUpCenterRect();
            } else {
                setUpColorRect();
            }
        }
    }

    @Override
    public void onRefreshed(Component component) {
        onSizeChanged(component.getEstimatedWidth(), component.getEstimatedHeight());
    }

    private void setUpCenterRect() {
        final Rect dRect = drawingRect;
        int left = dRect.left + borderWidthPx;
        int top = dRect.top + borderWidthPx;
        int bottom = dRect.bottom - borderWidthPx;
        int right = dRect.right - borderWidthPx;
        centerRect = new RectFloat(left, top, right, bottom);
    }

    private void setUpColorRect() {
        final Rect dRect = drawingRect;
        int left = dRect.left + borderWidthPx;
        int top = dRect.top + borderWidthPx;
        int bottom = dRect.bottom - borderWidthPx;
        int right = dRect.right - borderWidthPx;
        colorRect = new Rect(left, top, right, bottom);
        alphaPattern = new AlphaPatternDrawable(DrawingUtils.dpToPx(getContext(), 4));
        alphaPattern.setBounds(Math.round(colorRect.left), Math.round(colorRect.top), Math.round(colorRect.right),
                Math.round(colorRect.bottom));
    }

    /**
     * Get the color currently show by this view.
     *
     * @return the color value
     */
    public int getColor() {
        return color;
    }

    /**
     * Set the color that should be shown by this view.
     *
     * @param color the color value
     */
    public void setColor(int color) {
        this.color = color;
        invalidate();
    }

    /**
     * Set the original color. This is only used for previewing colors.
     *
     * @param color The original color
     */
    public void setOriginalColor(Color color) {
        if (originalPaint != null) {
            originalPaint.setColor(color);
        }
    }

    /**
     * @return the color of the border surrounding the panel.
     */
    public int getBorderColor() {
        return borderColor;
    }

    /**
     * Set the color of the border surrounding the panel.
     *
     * @param color the color value
     */
    public void setBorderColor(int color) {
        borderColor = color;
        invalidate();
    }

    /**
     * Get the shape
     *
     * @return Either {@link ColorShape#SQUARE} or {@link ColorShape#CIRCLE}.
     */
    @ColorShape
    public int getShape() {
        return shape;
    }

    /**
     * Set the shape.
     *
     * @param shape Either {@link ColorShape#SQUARE} or {@link ColorShape#CIRCLE}.
     */
    public void setShape(@ColorShape int shape) {
        this.shape = shape;
        invalidate();
    }

    /**
     * Show a toast message with the hex color code below the view.
     */
    public void showHint() {
        final Rect displayFrame = new Rect();
        final int[] screenPos = getLocationOnScreen();
        getWindowVisibleRect(displayFrame);
        final Context context = getContext();
        final int width = getWidth();
        final int height = getHeight();
        final int midy = screenPos[1] + height / 2;
        int referenceX = screenPos[0] + width / 2;

        if (getLayoutDirection() == Component.LayoutDirection.LTR) {
            final int screenWidth = displayFrame.getWidth();
            referenceX = screenWidth - referenceX; // mirror
        }
        StringBuilder hint = new StringBuilder("#");
        if (Color.alpha(color) != 255) {
            hint.append(Integer.toHexString(color).toUpperCase(Locale.ENGLISH));
        } else {
            hint.append(String.format("%06X", 0xFFFFFF & color).toUpperCase(Locale.ENGLISH));
        }
        ToastDialog cheatSheet = new ToastDialog(context).setText(hint.toString()).setDuration(2000);
        if (midy < displayFrame.getHeight()) { // maybe PopupDialog
            // Show along the top; follow action buttons
            cheatSheet.setAlignment(LayoutAlignment.TOP | LayoutAlignment.END);
            cheatSheet.setOffset(referenceX, screenPos[1] + height - displayFrame.top);
        } else {
            // Show along the bottom center
            cheatSheet.setAlignment(LayoutAlignment.BOTTOM | LayoutAlignment.HORIZONTAL_CENTER);
            cheatSheet.setOffset(0, height);
        }
        cheatSheet.show();
    }
}
